/*
 * Copyright 2010-2024 JetBrains s.r.o. and Kotlin Programming Language contributors.
 * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
 */

package org.jetbrains.kotlin.analysis.api.symbols

import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.analysis.api.lifetime.KaLifetimeOwner
import org.jetbrains.kotlin.analysis.api.symbols.markers.KaNamedSymbol
import org.jetbrains.kotlin.analysis.api.symbols.pointers.KaSymbolPointer
import org.jetbrains.kotlin.name.Name

public interface KaSymbol : KaLifetimeOwner {
    public val origin: KaSymbolOrigin

    public val location: KaSymbolLocation

    /**
     * [PsiElement] which corresponds to given [KaSymbol]
     * If [origin] is one of [KaSymbolOrigin.SOURCE], [KaSymbolOrigin.JAVA_SOURCE], [KaSymbolOrigin.JAVA_LIBRARY], [KaSymbolOrigin.LIBRARY] then the source is not null
     * For other [KaSymbolOrigin] behaviour is undefined
     *
     * For [KaSymbolOrigin.LIBRARY] the generated by Kotlin class file source element is returned
     */
    public val psi: PsiElement?

    public fun createPointer(): KaSymbolPointer<KaSymbol>
}

/**
 * Returns the name of the [KaSymbol] if it has it.
 */
public val KaSymbol.name: Name?
    get() = if (this is KaNamedSymbol) name else null

@Deprecated("Use 'KaSymbol' instead", replaceWith = ReplaceWith("KaSymbol"))
public typealias KtSymbol = KaSymbol

/**
 * Get symbol [PsiElement] if its type is [PSI], otherwise throws ClassCastException
 *
 * @see KaSymbol.psi
 */
public inline fun <reified PSI : PsiElement> KaSymbol.psi(): PSI =
    psi as PSI

/**
 * Get symbol [PsiElement] if its type is [PSI], otherwise null
 *
 * @see KaSymbol.psi
 */
public inline fun <reified PSI : PsiElement> KaSymbol.psiSafe(): PSI? =
    psi as? PSI

/**
 * Get symbol [PsiElement]. **null** if its [KaSymbol.origin] !is [KaSymbolOrigin.SOURCE]. throws **ClassCastException** if its type !is [PSI]
 *
 * @see KaSymbol.psi
 */
public inline fun <reified PSI : PsiElement> KaSymbol.sourcePsi(): PSI? {
    // TODO: support Java sources after KT-53669
    if (origin != KaSymbolOrigin.SOURCE) return null

    return psi as PSI
}

/**
 * Get symbol [PsiElement] if its type is [PSI] and [KaSymbol.origin] is [KaSymbolOrigin.SOURCE] or [KaSymbolOrigin.JAVA_SOURCE],
 * otherwise null
 *
 * @see KaSymbol.psiSafe
 */
public inline fun <reified PSI : PsiElement> KaSymbol.sourcePsiSafe(): PSI? {
    if (origin != KaSymbolOrigin.SOURCE && origin != KaSymbolOrigin.JAVA_SOURCE) return null

    return psi as? PSI
}

/**
 * A place where [KaSymbol] came from
 */
public enum class KaSymbolOrigin {
    /**
     * Declaration from Kotlin sources
     */
    SOURCE,

    /**
     * Declaration which do not have its PSI source and was generated, they are:
     * For regular classes, implicit default constructor is generated
     * For data classes the `copy`, `component{N}`, `toString`, `equals`, `hashCode` functions are generated
     * For enum classes the `valueOf` & `values` functions are generated
     * For lambda the `it` implicit parameter is generated
     */
    SOURCE_MEMBER_GENERATED,

    /**
     * A Kotlin declaration came from some .class file
     */
    LIBRARY,

    /**
     * A Kotlin declaration came from some Java source file
     */
    JAVA_SOURCE,

    /**
     * A Kotlin declaration came from some Java library
     */
    JAVA_LIBRARY,

    /**
     * A synthetic function that is called as a lambda argument when creating a SAM interface object, e.g.,
     * ```
     * val isEven = <caret>IntPredicate { it % 2 == 0 }
     * ```
     */
    SAM_CONSTRUCTOR,

    /**
     * Consider the following code:
     * ```
     * interface A { fun x() }
     * interface B { fun x() }
     *
     * interface C : A, B
     * ```
     * The intersection of functions A.foo & B.foo will create a function C.foo which will be marked with [INTERSECTION_OVERRIDE]
     */
    INTERSECTION_OVERRIDE,

    /**
     * Consider the following code:
     * ```
     * interface A<T> { fun x(): T }
     * interface B : A<Int> { }
     * ```
     *
     * The B will have a member B.foo(): Int, this member is generated inside interface B with substitution T -> Int,
     * such members are SUBSTITUTION_OVERRIDE
     */
    SUBSTITUTION_OVERRIDE,

    /**
     * Member symbol which was generated by compiler when using `by` interface delegation
     * e.g,
     * ```
     * interface A { fun x() }
     * class B(a: A) : A by a
     * ```
     * the `B.foo` function will be generated by Kotlin compiler
     */
    DELEGATED,


    JAVA_SYNTHETIC_PROPERTY,

    /**
     * Declaration is backing field of some member property
     * A symbol kind of [KaBackingFieldSymbol]
     *
     * @see KaBackingFieldSymbol
     */
    PROPERTY_BACKING_FIELD,

    /**
     * Declaration generated by the compiler plugin.
     *
     * @see org.jetbrains.kotlin.fir.declarations.FirDeclarationOrigin.Plugin
     */
    PLUGIN,


    /**
     * Declaration from dynamic Kotlin/JS scope.
     *
     * See: [Dynamic type Kotlin documentation](https://kotlinlang.org/docs/dynamic-type.html)
     */
    JS_DYNAMIC,

    /**
     * Kotlin/Native forward declaration.
     *
     * See: [Forward declarations in 1.9.20+](https://kotlinlang.org/docs/multiplatform-compatibility-guide.html#new-approach-to-forward-declarations)
     */
    NATIVE_FORWARD_DECLARATION,
}

@Deprecated("Use 'KaSymbolOrigin' instead", ReplaceWith("KaSymbolOrigin"))
public typealias KtSymbolOrigin = KaSymbolOrigin